/*
 * Copyright 2007 Ivan Dubrov
 * Copyright 2007, 2008 Robin Helgelin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.libermundi.theorcs.security.tapestry.internal;

import java.util.List;

import org.apache.tapestry5.annotations.BeginRender;
import org.apache.tapestry5.annotations.CleanupRender;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.FieldHandle;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticField;
import org.apache.tapestry5.plastic.PlasticMethod;
import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.util.SimpleMethodInvocation;

import com.google.common.collect.Lists;

/**
 * @author Ivan Dubrov
 * @author Martin Papy //Update to Plastic
 * 
 */
public class SpringSecurityWorker implements ComponentClassTransformWorker2 {

    private SecurityChecker securityChecker;

    public SpringSecurityWorker(final SecurityChecker securityChecker) {

        this.securityChecker = securityChecker;
    }

    

    @Override
	public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) {
    	
        model.addRenderPhase(BeginRender.class);
        model.addRenderPhase(CleanupRender.class);
        
        for(PlasticMethod method : plasticClass.getMethodsWithAnnotation(Secured.class)) {
        	transformMethod(plasticClass, method);
        }
        
        // Secure pages
        Secured annotation = plasticClass.getAnnotation(Secured.class);
        if (annotation != null) {
            transformPage(plasticClass, annotation);
        }
		
	}



	private void transformPage(final PlasticClass plasticClass, final Secured annotation) {
        
        // Interceptor token
        PlasticField tokenFieldInstance = plasticClass.introduceField(InterceptorStatusToken.class, "_$token");

        final FieldHandle tokenFieldAccess = tokenFieldInstance.getHandle();

        // Extend class
        PlasticMethod beginRenderMethod = plasticClass.introduceMethod(TransformConstants.BEGIN_RENDER_DESCRIPTION);


        final SecurityChecker secChecker = this.securityChecker;
        MethodAdvice beginRenderAdvice = new MethodAdvice() {

            @Override
			public void advise(MethodInvocation invocation) {
                invocation.proceed();

                InterceptorStatusToken statusTokenVal = secChecker.checkBefore(createAopMethodInvovation(invocation));
                tokenFieldAccess.set(invocation.getInstance(), statusTokenVal);
            }
        };

        beginRenderMethod.addAdvice(beginRenderAdvice);

        // ---------------- END TRANSFORMATION ------------------------


        PlasticMethod cleanupRenderMethod = plasticClass.introduceMethod(TransformConstants.CLEANUP_RENDER_DESCRIPTION);

        MethodAdvice cleanupRenderAdvice = new MethodAdvice() {
        	
        	@Override
            public void advise(MethodInvocation invocation) {
                invocation.proceed();

                // interField + ".checkAfter(" + tokenField + ", null);
                InterceptorStatusToken tokenFieldValue = (InterceptorStatusToken) tokenFieldAccess.get(invocation.getInstance());
                secChecker.checkAfter(tokenFieldValue, null);
            }
        };

        cleanupRenderMethod.addAdvice(cleanupRenderAdvice);

        // ------------- END TRANSFORMATION ------------------------

    }

    private void transformMethod(final PlasticClass plasticClass, final PlasticMethod method) {

        PlasticMethod securedMethod = plasticClass.introduceMethod(method.getDescription());

        PlasticField tokenFieldInstance = plasticClass.introduceField(InterceptorStatusToken.class, "_$token"); // InterceptorStatusToken
        
        final FieldHandle tokenFieldAccess = tokenFieldInstance.getHandle();

        final SecurityChecker secChecker = this.securityChecker;
        MethodAdvice securedMethodAdvice = new MethodAdvice() {
			
			@Override
			public void advise(MethodInvocation invocation) {
				InterceptorStatusToken statusTokenVal = secChecker.checkBefore(createAopMethodInvovation(invocation));
                tokenFieldAccess.set(invocation.getInstance(), statusTokenVal);
                
                invocation.proceed();

                InterceptorStatusToken tokenFieldValue = (InterceptorStatusToken) tokenFieldAccess.get(invocation.getInstance());
                secChecker.checkAfter(tokenFieldValue, null);
				
			}
		};

        securedMethod.addAdvice(securedMethodAdvice);
    }

    private static org.aopalliance.intercept.MethodInvocation createAopMethodInvovation(
			MethodInvocation methodInvocation) {
    	List<Object> arguments = Lists.newArrayList();
    	int index=0;
    	while(true){
    		try{
	    		arguments.add(methodInvocation.getParameter(index));
	    		index++;
    		} catch (IllegalArgumentException e) {
    			break;
    		}
    		
    	}
		return new SimpleMethodInvocation(methodInvocation.getInstance(),methodInvocation.getMethod(),arguments.toArray());
	}
}
